<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body></body>
  <script>
    // 存储副作用函数的桶
    const bucket = new WeakMap();
    const ITERATE_KEY = Symbol();

    /**定义一个  Map 实例，存储原始对象到代理对象的映射*/
    const reactiveMap = new Map();

    /**
     * 重写数组
     */
    // const arrayInstrumentations = {
    //   includes: function () {}
    // };

    /** 定义一个对象，将自定义的 add 方法定义到该对象下 */
    const mutableInstrumentations = {
      add(key) {
        // this 仍然指向的是代理对象，通过 raw 属性获取原始数据对象
        const target = this.raw;
        // 先判断值是否已经存在
        const hadKey = target.has(key);
        // 通过原始数据对象执行 add 方法添加具体的值
        const res = target.add(key);
        // 调用 trigger 函数触发响应，并指定操作类型为 ADD
        if (!hadKey) {
          trigger(target, key, "ADD");
        }
        return res;
      },
      delete(key) {
        // this 仍然指向的是代理对象，通过 raw 属性获取原始数据对象
        const target = this.raw;
        // 先判断值是否已经存在
        const hadKey = target.has(key);
        // 通过原始数据对象执行 add 方法添加具体的值
        const res = target.delete(ket);
        // 调用 trigger 函数触发响应，并指定操作类型为 ADD
        if (!hadKey) {
          trigger(target, key, "DELETE");
        }
        return res;
      },
    };

    const arrayInstrumentations = {
      includes: function () {},
    };

    /** 一个标记变量，代表是否进行追踪，默认值为 true, 即允许追踪 */
    let shouldTrack = true;
    /** 重新数组的 push 方法 */
    ["push", "pop", "shift", "unshift", "splice"].forEach((method) => {
      // 取得原始 push 方法
      const originMethod = Array.prototype[method];
      // 重写
      arrayInstrumentations[method] = function (...args) {
        // 在调用原始方法之前，禁止追踪
        shouldTrack = false;
        // push 方法的默认行为
        let res = originMethod.apply(this, args);
        // 在调用原始方法之后，恢复原来的行为，即允许追踪
        shouldTrack = true;
        return res;
      };
    });

    function reactive(obj) {
      // 优先通过原始对象 obj 寻找之前创建的代理对象，如果找到了，直接返回已有的代理对象
      const existionProxy = reactiveMap.get(obj);
      if (existionProxy) return existionProxy;
      // 否则创建新的代理对象
      const proxy = createReactive(obj);
      // 存储到 Map 中，从而避免重复创建
      reactiveMap.set(obj, proxy);
      return proxy;
    }
    function shallowReactive(obj) {
      return createReactive(obj, true);
    }

    function readonly(obj) {
      return createReactive(obj, false, true);
    }

    function shallowReadonly(obj) {
      return createReactive(obj, true, true);
    }

    function createReactive(obj, isShallow = false, isReadonly = false) {
      return new Proxy(obj, {
        // 拦截读取操作
        get(target, key, receiver) {
          if (key === "raw") {
            return target;
          }

          /**
           *  如果读取的是 size 属性
           *  通过指定第三个参数 receiver 为原始对象 target 从而修复问题
           */
          if (key === "size") {
            /** 调用 track 函数建立响应联系 */
            track(target, ITERATE_KEY);
            return Reflect.get(target, key, target);
          }

          /**
           * 如果操作的目标对象是数组，并且 key 存在于 arrayInstrumentations 上，
           * 那么返回定义在 arrayInstrumentations 上的值
           */

          if (
            Array.isArray(target) &&
            arrayInstrumentations.hasOwnProperty(key)
          ) {
            return Reflect.get(arrayInstrumentations, key, receiver);
          }

          // 非只读的时候才需要建立响应联系
          if (!isReadonly && typeof key !== "symbol") {
            track(target, key);
          }

          const res = mutableInstrumentations[key];
          if (isShallow) {
            return res;
          }

          if (typeof res === "object" && res !== null) {
            // 深只读/响应
            return isReadonly ? readonly(res) : reactive(res);
          }

          console.log("进来这里了", res);

          return res;
        },
        // 拦截设置操作
        set(target, key, newVal, receiver) {
          if (isReadonly) {
            return true;
          }
          const oldVal = target[key];
          // 如果属性不存在，则说明是在添加新的属性，否则是设置已存在的属性
          const type = Array.isArray(target)
            ? Number(key) < target.length
              ? "SET"
              : "ADD"
            : Object.prototype.hasOwnProperty.call(target, key)
            ? "SET"
            : "ADD";
          // 设置属性值
          const res = Reflect.set(target, key, newVal, receiver);
          if (target === receiver.raw) {
            if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
              trigger(target, key, type, newVal);
            }
          }

          return res;
        },
        has(target, key) {
          track(target, key);
          return Reflect.has(target, key);
        },
        ownKeys(target) {
          track(target, Array.isArray(target) ? "length" : ITERATE_KEY);
          return Reflect.ownKeys(target);
        },
        deleteProperty(target, key) {
          if (isReadonly) {
            return true;
          }
          const hadKey = Object.prototype.hasOwnProperty.call(target, key);
          const res = Reflect.deleteProperty(target, key);

          if (res && hadKey) {
            trigger(target, key, "DELETE");
          }

          return res;
        },
      });
    }

    function track(target, key) {
      // 当禁止追踪时，直接返回
      if (!activeEffect || !shouldTrack) return;
      let depsMap = bucket.get(target);
      if (!depsMap) {
        bucket.set(target, (depsMap = new Map()));
      }
      let deps = depsMap.get(key);
      if (!deps) {
        depsMap.set(key, (deps = new Set()));
      }
      deps.add(activeEffect);
      activeEffect.deps.push(deps);
    }

    function trigger(target, key, type, newVal) {
      const depsMap = bucket.get(target);
      if (!depsMap) return;
      const effects = depsMap.get(key);

      const effectsToRun = new Set();
      effects &&
        effects.forEach((effectFn) => {
          if (effectFn !== activeEffect) {
            effectsToRun.add(effectFn);
          }
        });

      if (type === "ADD" || type === "DELETE") {
        const iterateEffects = depsMap.get(ITERATE_KEY);
        iterateEffects &&
          iterateEffects.forEach((effectFn) => {
            if (effectFn !== activeEffect) {
              effectsToRun.add(effectFn);
            }
          });
      }

      if (type === "ADD" && Array.isArray(target)) {
        const lengthEffects = depsMap.get("length");
        lengthEffects &&
          lengthEffects.forEach((effectFn) => {
            if (effectFn !== activeEffect) {
              effectsToRun.add(effectFn);
            }
          });
      }

      if (Array.isArray(target) && key === "length") {
        depsMap.forEach((effects, key) => {
          if (key >= newVal) {
            effects.forEach((effectFn) => {
              if (effectFn !== activeEffect) {
                effectsToRun.add(effectFn);
              }
            });
          }
        });
      }

      effectsToRun.forEach((effectFn) => {
        if (effectFn.options.scheduler) {
          effectFn.options.scheduler(effectFn);
        } else {
          effectFn();
        }
      });
    }

    // 用一个全局变量存储当前激活的 effect 函数
    let activeEffect;
    // effect 栈
    const effectStack = [];

    function effect(fn, options = {}) {
      const effectFn = () => {
        cleanup(effectFn);
        // 当调用 effect 注册副作用函数时，将副作用函数复制给 activeEffect
        activeEffect = effectFn;
        // 在调用副作用函数之前将当前副作用函数压栈
        effectStack.push(effectFn);
        const res = fn();
        // 在当前副作用函数执行完毕后，将当前副作用函数弹出栈，并还原 activeEffect 为之前的值
        effectStack.pop();
        activeEffect = effectStack[effectStack.length - 1];

        return res;
      };
      // 将 options 挂在到 effectFn 上
      effectFn.options = options;
      // activeEffect.deps 用来存储所有与该副作用函数相关的依赖集合
      effectFn.deps = [];
      // 执行副作用函数
      if (!options.lazy) {
        effectFn();
      }

      return effectFn;
    }

    function cleanup(effectFn) {
      for (let i = 0; i < effectFn.deps.length; i++) {
        const deps = effectFn.deps[i];
        deps.delete(effectFn);
      }
      effectFn.deps.length = 0;
    }

    const p = reactive(new Set([1, 2, 3]));

    effect(() => {
      console.log("size 执行副作用函数", p.size);
    });

    p.add(1);
  </script>
</html>
